package com.westcom.healthmate.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.westcom.healthmate.bean.MiraSleepDailyEvaluate;
import com.westcom.healthmate.bean.MiraSleepDailyReport;
import com.westcom.healthmate.bean.MiraSleepDataBean;
import com.westcom.healthmate.bean.MiraToken;
import com.westcom.healthmate.common.constants.Constants;
import com.westcom.healthmate.common.constants.ErrorConstants;
import com.westcom.healthmate.common.exception.ErrorException;
import com.westcom.healthmate.common.network.http.HttpUtils;
import com.westcom.healthmate.common.network.http.Response;
import com.westcom.healthmate.common.utils.CommonUtils;
import com.westcom.healthmate.dao.MiraSleepDataDao;
import com.westcom.healthmate.dao.ThirdPartInfoDao;
import com.westcom.healthmate.po.MiraSleepDataPo;
import com.westcom.healthmate.po.ThirdPartInfoPo;
import com.westcom.healthmate.service.MiraService;
import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.*;


@Service
@PropertySource(value={"classpath:config/mira.properties"},name="mira.properties",ignoreResourceNotFound = false,encoding = "UTF-8")
public class MiraServiceImpl implements MiraService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Value("${ak}")
    private String ak;

    @Value("${sk}")
    private String sk;

    @Value("${token.url}")
    private String tokenUrl;

    @Value("${daily.report.url}")
    private String dailyReportUrl;

    @Value("${sleep.evaluate.url}")
    private String sleepEvaluateUrl;

    private Map<String, Object> getTokenParam = new HashMap<>();
    private MiraToken token = null;

    @Autowired
    ThirdPartInfoDao thirdPartInfoDao;

    @Autowired
    MiraSleepDataDao sleepDataDao;

    @PostConstruct
    private void init() {
        getTokenParam.put("access_key", ak);
        getTokenParam.put("secret_key", sk);
        this.token = getToken();
    }

    @Override
    public MiraSleepDailyEvaluate getSleepEvaluate(long userId, int sensorId, long st, long et) throws ErrorException
    {
        MiraSleepDailyReport report = getSleepReport(userId,sensorId,st,et);
        if(null == report)
        {
            throw new ErrorException(ErrorConstants.MIRA_SLEEP_DAILY_REPORT_NOT_FOUND);
        }
        MiraSleepDailyEvaluate evaluate = getMiraSleepEvaluate(report);
        if(null == evaluate)
        {
            throw new ErrorException(ErrorConstants.MIRA_SLEEP_DAILY_EVALUATE_NOT_FOUND);
        }
        evaluate.setMac(report.getMac());
        evaluate.setSensorId(sensorId);
        evaluate.setUserId(userId);
        return evaluate;
    }

    private MiraSleepDailyReport getMiraDailyReport(List<MiraSleepDataBean> data)
    {
        try
        {
            Response resp = HttpUtils.post(dailyReportUrl, null, new StringEntity(JSON.toJSONString(data)));
            if (!resp.isSuccessed())
            {
                logger.error("getMiraDailyReport failed,status:{},msg:{},body:{}", resp.getHttpCode(), resp.getErrorMsg(), resp.getBody());
                return null;
            }
            MiraSleepDailyReport report = JSON.parseObject(resp.getBody(),MiraSleepDailyReport.class);
            return report;
        } catch (Exception e)
        {
            e.printStackTrace();
            logger.error("getMiraDailyReport failed,msg:{}", e.getMessage());
            return null;
        }
    }

    private MiraSleepDailyEvaluate getMiraSleepEvaluate(MiraSleepDailyReport report)
    {
        MiraToken token = getToken();
        if(token  == null)
        {
            logger.error("get Mira sleep evaluate failed,token is null");
            return null;
        }
        Map<String,Object> param = new HashMap<>();
        param.put("token",token.getToken());
        param.put("asleep",report.getComment().getAsleep());
        param.put("rem",report.getComment().getRem());
        param.put("deep",report.getComment().getDeep());
        param.put("sleep_len",report.getComment().getSleepLen());
        try
        {
            Response resp = HttpUtils.get(HttpUtils.buildGetUrl(sleepEvaluateUrl,param),null);
            if (!resp.isSuccessed())
            {
                logger.error("getMiraSleepEvaluate failed,status:{},msg:{},body:{}", resp.getHttpCode(), resp.getErrorMsg(), resp.getBody());
                return null;
            }
            JSONObject jo = JSON.parseObject(resp.getBody());
            JSONObject dataJosn = jo.getJSONObject("data");
            if(null == dataJosn)
            {
                return null;
            }
            MiraSleepDailyEvaluate evaluate = dataJosn.toJavaObject(MiraSleepDailyEvaluate.class);
            return evaluate;
        } catch (ErrorException e)
        {
            e.printStackTrace();
            logger.error("getMiraSleepEvaluate failed,msg:{}", e.getMessage());
            return null;
        }
    }

    private List<MiraSleepDataBean> convertSleepData(Collection<MiraSleepDataPo> sleepData)
    {
        if (CollectionUtils.isEmpty(sleepData)) {
            return null;
        }
        List<MiraSleepDataBean> res = new ArrayList<>();
        sleepData.stream().forEach(po ->
        {
            res.add(new MiraSleepDataBean(po));
        });
        return res;
    }

    private MiraToken getToken()
    {
        if (null == token || token.isExpired())
        {
            Response resp = null;
            try
            {
                resp = HttpUtils.get(HttpUtils.buildGetUrl(tokenUrl, getTokenParam), null);
                if (!resp.isSuccessed())
                {
                    logger.error("getMiraToken failed,status:{},msg:{},body:{}", resp.getHttpCode(), resp.getErrorMsg(), resp.getBody());
                    this.token = null;
                    return null;
                }
            } catch (ErrorException e) {
                logger.error("get mira token failed,msg:{}", e.getMessage());
                e.printStackTrace();
                return null;
            }
            try
            {
                JSONObject o = JSONObject.parseObject(resp.getBody());
                String token = o.getJSONObject("data").getString("token");
                if (StringUtils.isEmpty(token))
                {
                    logger.error("get mira token failed");
                    return null;
                }
                this.token = new MiraToken(token);
            }catch (Exception e)
            {
                logger.error("parse mira token failed,msg:{}", e.getMessage());
                e.printStackTrace();
                return null;
            }
        }
        return this.token;
    }


    @Override
    public List<MiraSleepDataPo> saveDataFromBytes(String mac, byte[] data) throws ErrorException
    {
        try
        {
            if(StringUtils.isEmpty(mac))
            {
                throw new ErrorException(ErrorConstants.MAC_NOT_FOUND);
            }
            List<MiraSleepDataPo> pos = parseData(mac,data);
            sleepDataDao.save(pos);
            return pos;
        }catch (Exception e)
        {
            logger.error("save mira sleep data failed,msg:{}",e.getMessage());
            throw new ErrorException(ErrorConstants.INTERVAL_ERROR);
        }
    }

    @Override
    public MiraSleepDailyReport getSleepReport(long userId, int sensorId, long st, long et)throws ErrorException
    {
        ThirdPartInfoPo thirdPartInfo = thirdPartInfoDao.getInfoByUserIdAndThirdPartName(userId, Constants.GATEWAY);
        if (null == thirdPartInfo)
        {
            throw new ErrorException(ErrorConstants.THIRD_PART_INFO_NOT_FOUND);
        }
        List<MiraSleepDataPo> sleepData = sleepDataDao.getSleepDataByTimeSection(thirdPartInfo.getToken(),sensorId, st, et);
        List<MiraSleepDataBean> beans = convertSleepData(sleepData);
        if (CollectionUtils.isEmpty(beans))
        {
            throw new ErrorException(ErrorConstants.MIRA_SLEEP_DATA_NOT_FOUND);
        }
        MiraSleepDailyReport report = getMiraDailyReport(beans);
        if(null == report)
        {
            logger.error("mira report is null");
            throw new ErrorException(ErrorConstants.MIRA_SLEEP_DAILY_REPORT_NOT_FOUND);
        }
        report.setMac(thirdPartInfo.getToken());
        report.setSensorId(sensorId);
        report.setUserId(userId);
        return report;
    }

    private boolean fliterMiraSleepDataPo(MiraSleepDataPo po)
    {
        if(null == po)
        {
            return false;
        }
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(po.getTime()*1000);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        if(hour >9 && hour <20)
        {
            return false;
        }
        return true;
    }

    private List<MiraSleepDataPo> parseData(String mac, byte[] data) throws ErrorException
    {
        List<MiraSleepDataPo> res = new ArrayList<>();
        int offset = 0;
        long timeStamp = CommonUtils.bytes2Long(data,offset,Constants.TIMESTAMP_LEN);
        offset = offset + Constants.TIMESTAMP_LEN;
        long dataInterval = CommonUtils.bytes2Long(data,offset,Constants.SLEEP_DATA_INTERVAL_LEN);
        offset = offset + Constants.SLEEP_DATA_INTERVAL_LEN;
        long dataLen = CommonUtils.bytes2Long(data,offset,Constants.SLEEP_DATA_COUNT_LEN);
        offset = offset + Constants.SLEEP_DATA_COUNT_LEN;

        if(Constants.SLEEP_DATA_HEADER_LEN+dataLen*Constants.SLEEP_DATA_FRAME_LEN != data.length)
        {
            logger.error("invalid len,data frame:{},expect_total_len:{},actually_total_len:{},data:{}",
                    dataLen,
                    Constants.SLEEP_DATA_HEADER_LEN+dataLen*Constants.SLEEP_DATA_FRAME_LEN,
                    data.length,
                    CommonUtils.bytes2HexStr(data,data.length));
            throw new ErrorException("invalid format data");
        }
        for(int i = 0;i<dataLen;i++)
        {
            MiraSleepDataPo po = new MiraSleepDataPo();
            offset = Constants.SLEEP_DATA_HEADER_LEN+i* Constants.SLEEP_DATA_FRAME_LEN;

            //heart beat rate
            byte d = data[offset+0];
            po.setHeartBeatRate(CommonUtils.byte2double(d));

            //breath rate
            d = data[offset+1];
            po.setBreathRate(CommonUtils.byte2double(d));

            //bed power
            po.setOnBedPower(CommonUtils.bytes2Long(data,offset+2,2));

            //move rate
            d = data[offset+4];
            po.setBodyMoveRate((int) CommonUtils.byte2long(d));

            //sound val
            po.setSoundVal(CommonUtils.bytes2Long(data,offset+5,2));

            //
            d = data[offset+7];
            po.setSensorId((d & 0xf0)>>4);
            po.setIsBodyMoved(d & 0x01);
            po.setIsOnBed((d&0x02)>>1);
            po.setIsSnore((d&0x04)>>2);
            po.setIsSnoreRestrain((d&0x08)>>3);

            po.setMac(mac);
            po.setTime(timeStamp+i*dataInterval);
            logger.info("timeStamp:{}.dataInterval:{},dataLen:{},data:{}",timeStamp,dataInterval,dataLen,po.toString());

            if(fliterMiraSleepDataPo(po))
            {
                res.add(po);
            }
        }
        return res;
    }
}
